/* ARRAY's are flexible in dimension, can be made any size but take up
   no more room than necessary. The array can be accessed by ordinal
   but also by incrementing a pointer to an element as the data is
   terminated by a NULL entry. The data space is allocated in ->minlen
   increments to reduce xrealloc hits. */

/* Depends on xmalloc & xrealloc to be provided by the caller
   (Note: xrealloc must call xmalloc if handed a NULL pointer) */

/* Compile test program with:
   cc -g -o array array.c string.o -DTEST
   	or
   tcc -ml -DTEST=1 -c -I/tc/include array.c
   tcc -ml -L/tc/lib -oarray.exe array.obj string.obj

    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org

    Copyright (C) 1999-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

#include "mylib.h"

#define DEFAULT_ARRAY_CLUMP_SIZE 10

ARRAY newArray ARG2(INDEX, len, INDEX, clump)
{
    ARRAY	retval;

    retval = (ARRAY) xmalloc((INDEX) sizeof(struct ARRAY_S));
    retval->len = 0;
    retval->minlen = MAX(clump, 1);
    retval->maxlen = MAX(len, retval->minlen);
    retval->data = (void **) xmalloc((INDEX) sizeof(void *) * (retval->maxlen + 1));
    retval->data[0] = NULL;
    return(retval);
}
    
ARRAY expandArray ARG2(ARRAY, a, INDEX, inc)
{
    if (a == NULL)
	a = newArray(inc, DEFAULT_ARRAY_CLUMP_SIZE);
    else {
	INDEX	newlen = a->len + inc;

	if (a->maxlen < newlen) {
#ifdef ZAP
	    /* Round up to multiple of newlen */
	    newlen /= a->minlen;
	    newlen = (newlen + 1) * a->minlen;
#endif	    
	    a->data = (void **) xrealloc((char *)a->data, 
					 sizeof(void *) * (newlen + 1));
	    a->maxlen = newlen;
	}
    }
    return(a);
}

ARRAY addToArray ARG2(ARRAY, a, void *, item)
{
    a = expandArray(a, 1);
    a->data[(a->len)++] = item;
    a->data[a->len] = NULL;
    return(a);
}

/* Insert before itemnum */
ARRAY insertInArray ARG3(ARRAY, a, void *, item, INDEX, itemnum)
{
    INDEX	i;

    a = expandArray(a, 1);
    /* memcpy would be quicker here ... */
    for (i = a->len; i > itemnum; i--)
	a->data[i] = a->data[i - 1];
    a->data[itemnum] = item;
    a->data[++(a->len)] = NULL;
    return(a);
}

/* Sets the n'th item */
void setItem ARG3(ARRAY, a, INDEX, itemnum, void *, item)
{
    assert(a);
    assert(itemnum >= 0);
    assert(itemnum < a->len);
    assert(item);

    a->data[itemnum] = item;
}

/* Removes the item pointed to by itemp */
void **removeFromArray	ARG2(ARRAY, a, INDEX, itemnum)
{
    INDEX	i;
    
    assert(a);
    assert(itemnum >= 0);
    assert(itemnum < a->len);

    /* memcpy would be quicker ... */
    for (i = itemnum; i < a->len; i++)
	a->data[i] = a->data[i + 1];
    a->data[--(a->len)] = NULL;
#ifdef ZAP
    if (a->maxlen - a->len > a->minlen) {
	INDEX	newlen;
	
	newlen = a->len / a->minlen;
	newlen = (newlen + 1) * a->minlen;
	if (newlen)
	    a->data = (void **) xrealloc((char *)a->data, 
					 sizeof(void *) * (a->maxlen + 1));
    }
#endif
    return(a->data + itemnum);
}

/* Removes the item pointed to by itemp */
void **removeFromArrayp	ARG2(ARRAY, a, void **, itemp)
{
    INDEX	itemnum;

    assert(a);
    assert(itemp >= a->data);
    assert(itemp - a->data <= a->len);

    itemnum = itemp - a->data;
    return(removeFromArray(a, itemnum));
}

void removeAllItemsFromArray ARG2(ARRAY, a, f_ptr, destroyItem)
{
    INDEX		i;

    assert(a);
    assert(destroyItem);

    if (a->len) {
	for (i = 0; i < a->len; i++) {
	    (*destroyItem)(a->data[i]);
	    a->data[i] = NULL;
	}
	a->len = 0;
    }
}

/* Returns last item if itemnum == MAX_ITEM */
void *arrayItem ARG2(ARRAY, a, INDEX, itemnum)
{
    assert(a);
    if (a->len == 0)
	return(NULL);

    if (itemnum == MAX_INDEX)
	itemnum = a->len - 1;

    if ((itemnum >= 0) && (itemnum < a->len))
	return(a->data[itemnum]);
    else
	return(NULL);
}

/* Returns last item if itemnum == MAX_INDEX */
void **arrayItemp ARG2(ARRAY, a, INDEX, itemnum)
{
    assert(a);
    if (a->len == 0)
	return(NULL);

    if (itemnum == MAX_INDEX)
	itemnum = a->len - 1;

    if ((itemnum >= 0) && (itemnum < a->len))
	return(a->data + itemnum);
    else
	return(NULL);
}

void **lastArrayItemp ARG1(ARRAY, a)
{
    assert(a);

    if (a->len)
	return(a->data + a->len - 1);
    else
	return(NULL);
}

void **firstArrayItemp ARG1(ARRAY, a)
{
    assert(a);
    
    if (a->len)
	return(a->data);
    else
	return(NULL);
}

void **nextArrayItemp ARG2(ARRAY, a, void **, itemp)
{
    INDEX		itemnum;

    assert(a);
    assert(a->data);
    assert(itemp);
    assert(*itemp);

    itemnum = itemp - a->data;
    assert(itemnum >= 0);
    if (itemnum < a->len)
	return(itemp + 1);
    else
	return(NULL);
}

void **prevArrayItemp ARG2(ARRAY, a, void **, itemp)
{
    INDEX	itemnum;

    assert(a);
    assert(a->data);
    assert(itemp);

    if (itemp == a->data)
	return(NULL);

    assert(itemp > a->data);

    itemnum = itemp - a->data;
    
    assert(itemnum > 0);
    assert(itemnum <= a->len);
    
    return(itemp - 1);
}

INDEX arrayLength ARG1(ARRAY, a)
{
    if (a)
	return(a->len);
    else
	return(0);
}

#ifdef TEST

#include <stdio.h>

/* extern jmp_buf		NoMoreHeap; 
  - don't bother with exceptions for the test program */

char *xmalloc ARG1(INDEX , size)
{
    register char 	*retval;

    if ((retval = malloc((size_t) size)))
	return(retval);
    /* Problem - no more heap */
    /* longjmp(NoMoreHeap, 1); */
    return(NULL);
}

char *xrealloc ARG2(char *, oldbuf, INDEX, size)
{
    register char 	*retval;

    if (oldbuf) {
	if ((retval = realloc(oldbuf, (size_t) size)))
	    return(retval);
    } else
	return(xmalloc((INDEX) size));

    /* Problem - no more heap */
    /* longjmp(NoMoreHeap, 1); */
    return(NULL);
}

void printfStr ARG1(STRING, s)
{
    if (s)
	printf("\"%s\" length = %d, maxlen = %d\n", 
	       StringToStr(s), StringLength(s), StringMaxLength(s));
}

void printfArr ARG1(ARRAY, arr)
{
    STRING	*s;

    if (arr) {
#ifdef SIZE_T_IS_INT
	printf("-S- length=%d, maxlen=%d\n", arrayLength(arr), arr->maxlen);
#else
	printf("-S- length=%ld, maxlen=%ld\n", arrayLength(arr), arr->maxlen);
#endif
	for (s = (STRING *)firstArrayItemp(arr);
	     s && *s; 
	     s = (STRING *)nextArrayItemp(arr, (void **) s))
	    printfStr(*s);
	printf("-E-\n");
    }
}

main(argc, argv)
    int		argc;
    char	**argv;
{
    STRING 	a,b,c, *sp;
    ARRAY	arr;
    int		i, iterations;

    if (argc > 1)
        iterations = atoi(argv[1]);
    else
        iterations = 100;

    printf("\nArrays %d\n", iterations);
    arr = newArray(1, 1);

    a = StrToString(NULL, "Bottom a");
    b = StrToString(NULL, "Bottom b");
    c = StrToString(NULL, "Bottom c");

    arr = addToArray(arr, a);
    printfArr(arr);
    arr = addToArray(arr, b);
    printfArr(arr);
    insertInArray(arr, c, 1);
    printfArr(arr);

    removeFromArray(arr, 2);
    printfArr(arr);

    sp = (STRING *) firstArrayItemp(arr);
    sp = (STRING *) removeFromArrayp(arr, (void **) sp);
    printfArr(arr);
/*
    freeString(a);
    freeString(b);
    freeString(c);
*/

    removeAllItemsFromArray(arr, (f_ptr) freeStringF);
    freeArray(arr);
    
    for (i = 0; i < iterations; i++) {
        a = StrToString(NULL, "A test string which is a bit longer than before");
	arr = addToArray(arr, a);
    }

    for (i = 0; i < iterations; i++) {
        removeFromArray(arr, 0);
    }
}
#endif

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
